/*
 * libxl_capabilities.c: libxl capabilities generation
 *
 * Copyright (C) 2016 SUSE LINUX Products GmbH, Nuernberg, Germany.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include <config.h>

#include <libxl.h>

#include "internal.h"
#include "virlog.h"
#include "virerror.h"
#include "virfile.h"
#include "viralloc.h"
#include "domain_conf.h"
#include "capabilities.h"
#include "domain_capabilities.h"
#include "vircommand.h"
#include "libxl_capabilities.h"
#include "cpu/cpu_x86.h"
#include "cpu/cpu_x86_data.h"


#define VIR_FROM_THIS VIR_FROM_LIBXL

VIR_LOG_INIT("libxl.libxl_capabilities");

/* see xen-unstable.hg/xen/include/asm-x86/cpufeature.h */
#define LIBXL_X86_FEATURE_PAE_MASK (1 << 6)
#define LIBXL_X86_FEATURE_LM_MASK  (1 << 29)

struct guest_arch {
    virArch arch;
    int hvm;
    int pvh;
    int pae;
    int nonpae;
    int ia64_be;
};

#define XEN_CAP_REGEX "(xen|hvm)-[[:digit:]]+\\.[[:digit:]]+-(aarch64|armv7l|x86_32|x86_64|ia64|powerpc64)(p|be)?"

static void
libxlCapsAddCPUID(virCPUData *data, virCPUx86CPUID *cpuid, ssize_t ncaps)
{
    virCPUx86DataItem item = { 0 };
    size_t i;

    item.type = VIR_CPU_X86_DATA_CPUID;
    for (i = 0; i < ncaps; i++) {
        item.data.cpuid = cpuid[i];

        virCPUx86DataAdd(data, &item);
    }
}

/*
 * The words represented in physinfo.hw_cap are host CPUID (sub) leafs.
 * Position of these hasn't changed much up until Xen 4.7 with a rework
 * on how CPUID is handled internally. As a side-effect it got normalized
 * and also added more feature words. Although cannot be relied upon as
 * stable interface, and hence we version changes in position of the features
 * across all supported versions of the libxl driver until libxl exposes a
 * stable representation of these capabilities. Fortunately not a lot of
 * variation happened so it's still trivial to keep track of these leafs
 * to describe host CPU in libvirt capabilities.
 *
 *              |       Xen >= 4.7     |
 *              ------------------------
 *       word 0 | CPUID.00000001.EDX   |
 *       word 1 | CPUID.00000001.ECX   |
 *       word 2 | CPUID.80000001.EDX   |
 *       word 3 | CPUID.80000001.ECX   |
 *       word 4 | CPUID.0000000D:1.EAX |
 *       word 5 | CPUID.00000007:0.EBX |
 *       word 6 | CPUID.00000007:0.ECX |
 *       word 7 | CPUID.80000007.EDX   |
 *       word 8 | CPUID.80000008.EBX   |
 *
 */
static virCPUData *
libxlCapsNodeData(virCPUDef *cpu, libxl_hwcap hwcap)
{
    ssize_t ncaps;
    g_autoptr(virCPUData) cpudata = NULL;
    virCPUx86CPUID cpuid[] = {
        { .eax_in = 0x00000001, .edx = hwcap[0] },
        { .eax_in = 0x00000001, .ecx = hwcap[1] },
        { .eax_in = 0x80000001, .edx = hwcap[2] },
        { .eax_in = 0x80000001, .ecx = hwcap[3] },
        { .eax_in = 0x00000007, .ebx = hwcap[5] },
        { .eax_in = 0x0000000D, .ecx_in = 1U, .eax = hwcap[4] },
        { .eax_in = 0x00000007, .ecx_in = 0U, .ecx = hwcap[6] },
        { .eax_in = 0x80000007, .ecx_in = 0U, .edx = hwcap[7] },
    };

    if (!(cpudata = virCPUDataNew(cpu->arch)))
        return NULL;

    ncaps = G_N_ELEMENTS(cpuid);
    libxlCapsAddCPUID(cpudata, cpuid, ncaps);

    return g_steal_pointer(&cpudata);
}

/* hw_caps is an array of 32-bit words whose meaning is listed in
 * xen-unstable.hg/xen/include/asm-x86/cpufeature.h.  Each feature
 * is defined in the form X*32+Y, corresponding to the Y'th bit in
 * the X'th 32-bit word of hw_cap.
 */
static int
libxlCapsInitCPU(virCaps *caps, libxl_physinfo *phy_info)
{
    g_autoptr(virCPUData) data = NULL;
    g_autoptr(virCPUDef) cpu = NULL;
    int host_pae;
    int host_lm;

    /* On ARM hw_cap vector is zeroed out but not on x86 */
    if (!phy_info->hw_cap[0])
        return 0;

    cpu = virCPUDefNew();

    host_pae = phy_info->hw_cap[0] & LIBXL_X86_FEATURE_PAE_MASK;
    if (host_pae &&
        virCapabilitiesAddHostFeature(caps, "pae") < 0)
        return -1;

    host_lm = (phy_info->hw_cap[2] & LIBXL_X86_FEATURE_LM_MASK);
    if (host_lm)
        cpu->arch = VIR_ARCH_X86_64;
    else
        cpu->arch = VIR_ARCH_I686;

    cpu->type = VIR_CPU_TYPE_HOST;
    cpu->cores = phy_info->cores_per_socket;
    cpu->threads = phy_info->threads_per_core;
    cpu->dies = 1;
    cpu->clusters = 1;
    cpu->sockets = phy_info->nr_cpus / (cpu->cores * cpu->threads);

    if (!(data = libxlCapsNodeData(cpu, phy_info->hw_cap)) ||
        cpuDecode(cpu, data, NULL) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Failed to initialize host cpu features"));
        return -1;
    }

    caps->host.cpu = g_steal_pointer(&cpu);
    return 0;
}

static int
libxlCapsInitHost(libxl_ctx *ctx, virCaps *caps)
{
    libxl_physinfo phy_info;
    int ret = -1;

    libxl_physinfo_init(&phy_info);
    if (libxl_get_physinfo(ctx, &phy_info) != 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Failed to get node physical info from libxenlight"));
        goto cleanup;
    }

    if (libxlCapsInitCPU(caps, &phy_info) < 0)
        goto cleanup;

    if (virCapabilitiesSetNetPrefix(caps, LIBXL_GENERATED_PREFIX_XEN) < 0)
        goto cleanup;

    ret = 0;

 cleanup:
    libxl_physinfo_dispose(&phy_info);
    return ret;
}

static int
libxlCapsInitNuma(libxl_ctx *ctx, virCaps *caps)
{
    libxl_numainfo *numa_info = NULL;
    libxl_cputopology *cpu_topo = NULL;
    int nr_nodes = 0, nr_cpus = 0, nr_distances = 0;
    virCapsHostNUMACellCPU **cpus = NULL;
    virNumaDistance *distances = NULL;
    int *nr_cpus_node = NULL;
    size_t i;
    int ret = -1;

    /* Let's try to fetch all the topology information */
    numa_info = libxl_get_numainfo(ctx, &nr_nodes);
    if (numa_info == NULL || nr_nodes == 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("libxl_get_numainfo failed"));
        goto cleanup;
    }

    cpu_topo = libxl_get_cpu_topology(ctx, &nr_cpus);
    if (cpu_topo == NULL || nr_cpus == 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("libxl_get_cpu_topology failed"));
        goto cleanup;
    }

    cpus = g_new0(virCapsHostNUMACellCPU *, nr_nodes);

    nr_cpus_node = g_new0(int, nr_nodes);

    /* For each node, prepare a list of CPUs belonging to that node */
    for (i = 0; i < nr_cpus; i++) {
        int node = cpu_topo[i].node;

        if (cpu_topo[i].core == LIBXL_CPUTOPOLOGY_INVALID_ENTRY)
            continue;

        nr_cpus_node[node]++;

        if (nr_cpus_node[node] == 1) {
            cpus[node] = g_new0(virCapsHostNUMACellCPU, 1);
        } else {
            VIR_REALLOC_N(cpus[node], nr_cpus_node[node]);
        }

        /* Mapping between what libxl tells and what libvirt wants */
        cpus[node][nr_cpus_node[node]-1].id = i;
        cpus[node][nr_cpus_node[node]-1].socket_id = cpu_topo[i].socket;
        cpus[node][nr_cpus_node[node]-1].core_id = cpu_topo[i].core;
        /* Until Xen reports die_id, 0 is better than random garbage */
        cpus[node][nr_cpus_node[node]-1].die_id = 0;
        /* Allocate the siblings maps. We will be filling them later */
        cpus[node][nr_cpus_node[node]-1].siblings = virBitmapNew(nr_cpus);
    }

    /* Let's now populate the siblings bitmaps */
    for (i = 0; i < nr_cpus; i++) {
        int node = cpu_topo[i].node;
        size_t j;

        if (cpu_topo[i].core == LIBXL_CPUTOPOLOGY_INVALID_ENTRY)
            continue;

        for (j = 0; j < nr_cpus_node[node]; j++) {
            if (cpus[node][j].socket_id == cpu_topo[i].socket &&
                cpus[node][j].core_id == cpu_topo[i].core)
                ignore_value(virBitmapSetBit(cpus[node][j].siblings, i));
        }
    }

    caps->host.numa = virCapabilitiesHostNUMANew();
    for (i = 0; i < nr_nodes; i++) {
        if (numa_info[i].size == LIBXL_NUMAINFO_INVALID_ENTRY)
            continue;

        nr_distances = numa_info[i].num_dists;
        if (nr_distances) {
            size_t j;

            distances = g_new0(virNumaDistance, nr_distances);

            for (j = 0; j < nr_distances; j++) {
                distances[j].cellid = j;
                distances[j].value = numa_info[i].dists[j];
            }
        }

        virCapabilitiesHostNUMAAddCell(caps->host.numa, i,
                                       numa_info[i].size / 1024,
                                       nr_cpus_node[i], &cpus[i],
                                       nr_distances, &distances,
                                       0, NULL,
                                       NULL);

        /* This is safe, as the CPU list is now stored in the NUMA cell */
        cpus[i] = NULL;
    }

    ret = 0;

 cleanup:
    if (ret != 0) {
        for (i = 0; cpus && i < nr_nodes; i++)
            VIR_FREE(cpus[i]);
        if (caps->host.numa) {
            g_clear_pointer(&caps->host.numa, virCapabilitiesHostNUMAUnref);
        }
        VIR_FREE(distances);
    }

    VIR_FREE(cpus);
    VIR_FREE(nr_cpus_node);
    libxl_cputopology_list_free(cpu_topo, nr_cpus);
    libxl_numainfo_list_free(numa_info, nr_nodes);

    return ret;
}

static int
libxlCapsInitGuests(libxl_ctx *ctx, virCaps *caps)
{
    const libxl_version_info *ver_info;
    g_autoptr(GRegex) regex = NULL;
    g_autoptr(GError) err = NULL;
    g_autoptr(GMatchInfo) info = NULL;
    char *str, *token;
    char *saveptr = NULL;
    size_t i;

    struct guest_arch guest_archs[32] = { 0 };
    int nr_guest_archs = 0;

    if ((ver_info = libxl_get_version_info(ctx)) == NULL) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Failed to get version info from libxenlight"));
        return -1;
    }

    if (!ver_info->capabilities) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Failed to get capabilities from libxenlight"));
        return -1;
    }

    regex = g_regex_new(XEN_CAP_REGEX, 0, 0, &err);
    if (!regex) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Failed to compile regex %1$s"), err->message);
        return -1;
    }

    /* Format of capabilities string is documented in the code in
     * xen-unstable.hg/xen/arch/.../setup.c.
     *
     * It is a space-separated list of supported guest architectures.
     *
     * For x86:
     *    TYP-VER-ARCH[p]
     *    ^   ^   ^    ^
     *    |   |   |    +-- PAE supported
     *    |   |   +------- x86_32 or x86_64
     *    |   +----------- the version of Xen, eg. "3.0"
     *    +--------------- "xen" or "hvm" for para or full virt respectively
     *
     * For IA64:
     *    TYP-VER-ARCH[be]
     *    ^   ^   ^    ^
     *    |   |   |    +-- Big-endian supported
     *    |   |   +------- always "ia64"
     *    |   +----------- the version of Xen, eg. "3.0"
     *    +--------------- "xen" or "hvm" for para or full virt respectively
     */

    /* Split capabilities string into tokens. strtok_r is OK here because
     * we "own" the buffer.  Parse out the features from each token.
     */
    for (str = ver_info->capabilities, nr_guest_archs = 0;
         nr_guest_archs < G_N_ELEMENTS(guest_archs)
                 && (token = strtok_r(str, " ", &saveptr)) != NULL;
         str = NULL) {
        if (g_regex_match(regex, token, 0, &info)) {
            g_autofree char *modestr = g_match_info_fetch(info, 1);
            g_autofree char *archstr = g_match_info_fetch(info, 2);
            g_autofree char *suffixstr = g_match_info_fetch(info, 3);
            int hvm = STRPREFIX(modestr, "hvm");
            virArch arch;
            int pae = 0, nonpae = 0, ia64_be = 0;

            if (STRPREFIX(archstr, "x86_32")) {
                arch = VIR_ARCH_I686;
                if (suffixstr != NULL && STRPREFIX(suffixstr, "p"))
                    pae = 1;
                else
                    nonpae = 1;
            } else if (STRPREFIX(archstr, "x86_64")) {
                arch = VIR_ARCH_X86_64;
            } else if (STRPREFIX(archstr, "ia64")) {
                arch = VIR_ARCH_ITANIUM;
                if (suffixstr != NULL && STRPREFIX(suffixstr, "be"))
                    ia64_be = 1;
            } else if (STRPREFIX(archstr, "powerpc64")) {
                arch = VIR_ARCH_PPC64;
            } else if (STRPREFIX(archstr, "armv7l")) {
                arch = VIR_ARCH_ARMV7L;
            } else if (STRPREFIX(archstr, "aarch64")) {
                arch = VIR_ARCH_AARCH64;
            } else {
                continue;
            }

            /* Search for existing matching (model,hvm) tuple */
            for (i = 0; i < nr_guest_archs; i++) {
                if ((guest_archs[i].arch == arch) &&
                    guest_archs[i].hvm == hvm)
                    break;
            }

            /* Too many arch flavours - highly unlikely ! */
            if (i >= G_N_ELEMENTS(guest_archs))
                continue;
            /* Didn't find a match, so create a new one */
            if (i == nr_guest_archs)
                nr_guest_archs++;

            guest_archs[i].arch = arch;
            guest_archs[i].hvm = hvm;

            /* Careful not to overwrite a previous positive
               setting with a negative one here - some archs
               can do both pae & non-pae, but Xen reports
               separately capabilities so we're merging archs */
            if (pae)
                guest_archs[i].pae = pae;
            if (nonpae)
                guest_archs[i].nonpae = nonpae;
            if (ia64_be)
                guest_archs[i].ia64_be = ia64_be;

            /*
             * Xen 4.10 introduced support for the PVH guest type, which
             * requires hardware virtualization support similar to the
             * HVM guest type. Add a PVH guest type for each new HVM
             * guest type.
             */
#ifdef WITH_XEN_PVH
            if (hvm && i == nr_guest_archs-1) {
                /* Ensure we have not exhausted the guest_archs array */
                if (nr_guest_archs >= G_N_ELEMENTS(guest_archs))
                    continue;
                i = nr_guest_archs;
                nr_guest_archs++;

                guest_archs[i].arch = arch;
                guest_archs[i].hvm = 0;
                guest_archs[i].pvh = 1;
            }
#endif
        }
    }

    for (i = 0; i < nr_guest_archs; ++i) {
        virCapsGuest *guest;
        virCapsGuestMachine **machines;
        int nmachines;
        virDomainOSType ostype = VIR_DOMAIN_OSTYPE_XEN;
        const char *loader = NULL;

        if (guest_archs[i].hvm) {
            char const *const xen_machines[] = { "xenfv", NULL };

            ostype = VIR_DOMAIN_OSTYPE_HVM;
            loader = LIBXL_FIRMWARE_DIR "/hvmloader";

            machines = virCapabilitiesAllocMachines(xen_machines, &nmachines);
        } else if (guest_archs[i].pvh) {
            char const *const xen_machines[] = { "xenpvh", NULL };

            ostype = VIR_DOMAIN_OSTYPE_XENPVH;
            machines = virCapabilitiesAllocMachines(xen_machines, &nmachines);
        } else {
            char const *const xen_machines[] = { "xenpv", NULL };

            ostype = VIR_DOMAIN_OSTYPE_XEN;
            machines = virCapabilitiesAllocMachines(xen_machines, &nmachines);
        }

        guest = virCapabilitiesAddGuest(caps, ostype, guest_archs[i].arch,
                                        LIBXL_EXECBIN_DIR "/qemu-system-i386",
                                        loader, nmachines, machines);

        virCapabilitiesAddGuestDomain(guest, VIR_DOMAIN_VIRT_XEN,
                                      NULL, NULL, 0, NULL);

        if (guest_archs[i].pae)
            virCapabilitiesAddGuestFeature(guest, VIR_CAPS_GUEST_FEATURE_TYPE_PAE);

        if (guest_archs[i].nonpae)
            virCapabilitiesAddGuestFeature(guest, VIR_CAPS_GUEST_FEATURE_TYPE_NONPAE);

        if (guest_archs[i].ia64_be)
            virCapabilitiesAddGuestFeature(guest, VIR_CAPS_GUEST_FEATURE_TYPE_IA64_BE);

        if (guest_archs[i].hvm) {
            virCapabilitiesAddGuestFeatureWithToggle(guest, VIR_CAPS_GUEST_FEATURE_TYPE_ACPI,
                                                     true, true);

            virCapabilitiesAddGuestFeatureWithToggle(guest, VIR_CAPS_GUEST_FEATURE_TYPE_APIC,
                                                     true, false);
        }

        if (guest_archs[i].hvm || guest_archs[i].pvh) {
            virCapabilitiesAddGuestFeatureWithToggle(guest, VIR_CAPS_GUEST_FEATURE_TYPE_HAP,
                                                     true, true);
        }
    }

    return 0;
}

static int
libxlMakeDomainOSCaps(const char *machine,
                      virDomainCapsOS *os,
                      virFirmware **firmwares,
                      size_t nfirmwares)
{
    virDomainCapsLoader *capsLoader = &os->loader;
    size_t i;

    os->supported = VIR_TRISTATE_BOOL_YES;
    capsLoader->supported = VIR_TRISTATE_BOOL_NO;
    capsLoader->type.report = true;
    capsLoader->readonly.report = true;

    if (STREQ(machine, "xenpv") || STREQ(machine, "xenpvh"))
        return 0;

    capsLoader->supported = VIR_TRISTATE_BOOL_YES;
    capsLoader->values.values = g_new0(char *, nfirmwares);

    for (i = 0; i < nfirmwares; i++) {
        capsLoader->values.values[capsLoader->values.nvalues] = g_strdup(firmwares[i]->name);
        capsLoader->values.nvalues++;
    }

    VIR_DOMAIN_CAPS_ENUM_SET(capsLoader->type,
                             VIR_DOMAIN_LOADER_TYPE_ROM,
                             VIR_DOMAIN_LOADER_TYPE_PFLASH);
    VIR_DOMAIN_CAPS_ENUM_SET(capsLoader->readonly,
                             VIR_TRISTATE_BOOL_YES);

    return 0;
}

static int
libxlMakeDomainDeviceDiskCaps(virDomainCapsDeviceDisk *dev)
{
    dev->supported = VIR_TRISTATE_BOOL_YES;
    dev->diskDevice.report = true;
    dev->bus.report = true;
    dev->model.report = true;

    VIR_DOMAIN_CAPS_ENUM_SET(dev->diskDevice,
                             VIR_DOMAIN_DISK_DEVICE_DISK,
                             VIR_DOMAIN_DISK_DEVICE_CDROM);

    VIR_DOMAIN_CAPS_ENUM_SET(dev->bus,
                             VIR_DOMAIN_DISK_BUS_IDE,
                             VIR_DOMAIN_DISK_BUS_SCSI,
                             VIR_DOMAIN_DISK_BUS_XEN);

    return 0;
}

static int
libxlMakeDomainDeviceGraphicsCaps(virDomainCapsDeviceGraphics *dev)
{
    dev->supported = VIR_TRISTATE_BOOL_YES;
    dev->type.report = true;

    VIR_DOMAIN_CAPS_ENUM_SET(dev->type,
                             VIR_DOMAIN_GRAPHICS_TYPE_SDL,
                             VIR_DOMAIN_GRAPHICS_TYPE_VNC,
                             VIR_DOMAIN_GRAPHICS_TYPE_SPICE);

    return 0;
}

static int
libxlMakeDomainDeviceVideoCaps(virDomainCapsDeviceVideo *dev)
{
    dev->supported = VIR_TRISTATE_BOOL_YES;
    dev->modelType.report = true;

    VIR_DOMAIN_CAPS_ENUM_SET(dev->modelType,
                             VIR_DOMAIN_VIDEO_TYPE_VGA,
                             VIR_DOMAIN_VIDEO_TYPE_CIRRUS,
                             VIR_DOMAIN_VIDEO_TYPE_XEN);

    return 0;
}

static int
libxlMakeDomainDeviceHostdevCaps(virDomainCapsDeviceHostdev *dev)
{
    dev->supported = VIR_TRISTATE_BOOL_YES;
    dev->mode.report = true;
    dev->startupPolicy.report = true;
    dev->subsysType.report = true;
    dev->capsType.report = true;
    dev->pciBackend.report = true;

    /* VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES is for containers only */
    VIR_DOMAIN_CAPS_ENUM_SET(dev->mode,
                             VIR_DOMAIN_HOSTDEV_MODE_SUBSYS);

    VIR_DOMAIN_CAPS_ENUM_SET(dev->startupPolicy,
                             VIR_DOMAIN_STARTUP_POLICY_DEFAULT,
                             VIR_DOMAIN_STARTUP_POLICY_MANDATORY,
                             VIR_DOMAIN_STARTUP_POLICY_REQUISITE,
                             VIR_DOMAIN_STARTUP_POLICY_OPTIONAL);

    VIR_DOMAIN_CAPS_ENUM_SET(dev->subsysType,
                             VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI);

    VIR_DOMAIN_CAPS_ENUM_SET(dev->subsysType,
                             VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB);

    /* No virDomainHostdevCapsType for libxl */
    virDomainCapsEnumClear(&dev->capsType);

    virDomainCapsEnumClear(&dev->pciBackend);
    VIR_DOMAIN_CAPS_ENUM_SET(dev->pciBackend, VIR_DEVICE_HOSTDEV_PCI_DRIVER_NAME_XEN);
    return 0;
}

virCaps *
libxlMakeCapabilities(libxl_ctx *ctx)
{
    g_autoptr(virCaps) caps = NULL;

#ifdef LIBXL_HAVE_NO_SUSPEND_RESUME
    if ((caps = virCapabilitiesNew(virArchFromHost(), false, false)) == NULL)
#else
    if ((caps = virCapabilitiesNew(virArchFromHost(), true, true)) == NULL)
#endif
        return NULL;

    if (libxlCapsInitHost(ctx, caps) < 0)
        return NULL;

    if (libxlCapsInitNuma(ctx, caps) < 0)
        return NULL;

    if (libxlCapsInitGuests(ctx, caps) < 0)
        return NULL;

    return g_steal_pointer(&caps);
}

/*
 * Currently Xen has no interface to report maxvcpus supported
 * for the various domain types (PV, HVM, PVH). HVM_MAX_VCPUS
 * is defined in $xensrc/xen/include/public/hvm/hvm_info_table.h
 * PV has no equivalent and is relunctantly set here until Xen
 * can report such capabilities.
 */
#define HVM_MAX_VCPUS 128
#define PV_MAX_VCPUS  512

int
libxlMakeDomainCapabilities(virDomainCaps *domCaps,
                            virFirmware **firmwares,
                            size_t nfirmwares)
{
    virDomainCapsOS *os = &domCaps->os;
    virDomainCapsDeviceDisk *disk = &domCaps->disk;
    virDomainCapsDeviceGraphics *graphics = &domCaps->graphics;
    virDomainCapsDeviceVideo *video = &domCaps->video;
    virDomainCapsDeviceHostdev *hostdev = &domCaps->hostdev;

    if (STREQ(domCaps->machine, "xenfv"))
        domCaps->maxvcpus = HVM_MAX_VCPUS;
    else
        domCaps->maxvcpus = PV_MAX_VCPUS;

    if (libxlMakeDomainOSCaps(domCaps->machine, os, firmwares, nfirmwares) < 0 ||
        libxlMakeDomainDeviceDiskCaps(disk) < 0 ||
        libxlMakeDomainDeviceGraphicsCaps(graphics) < 0 ||
        libxlMakeDomainDeviceVideoCaps(video) < 0)
        return -1;
    if (STRNEQ(domCaps->machine, "xenpvh") &&
        libxlMakeDomainDeviceHostdevCaps(hostdev) < 0)
        return -1;

    domCaps->features[VIR_DOMAIN_CAPS_FEATURE_IOTHREADS] = VIR_TRISTATE_BOOL_NO;
    domCaps->features[VIR_DOMAIN_CAPS_FEATURE_VMCOREINFO] = VIR_TRISTATE_BOOL_NO;
    domCaps->features[VIR_DOMAIN_CAPS_FEATURE_GENID] = VIR_TRISTATE_BOOL_NO;
    domCaps->gic.supported = VIR_TRISTATE_BOOL_NO;

    return 0;
}

#define LIBXL_QEMU_DM_STR  "Options specific to the Xen version:"

int
libxlDomainGetEmulatorType(const virDomainDef *def)
{
    int ret = LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN;
    g_autoptr(virCommand) cmd = NULL;
    g_autofree char *output = NULL;

    if (def->os.type == VIR_DOMAIN_OSTYPE_HVM) {
        if (def->emulator) {
            if (!virFileExists(def->emulator))
                return ret;

            cmd = virCommandNew(def->emulator);

            virCommandAddArgList(cmd, "-help", NULL);
            virCommandSetOutputBuffer(cmd, &output);

            if (virCommandRun(cmd, NULL) < 0)
                return ret;

            if (strstr(output, LIBXL_QEMU_DM_STR))
                ret = LIBXL_DEVICE_MODEL_VERSION_QEMU_XEN_TRADITIONAL;
        }
    }

    return ret;
}
